(function() {
  var isArray = Array.isArray;

  var root = this;

  function EventEmitter() {}


  if (typeof module !== 'undefined' && module.exports) {
    module.exports.EventEmitter = EventEmitter;
  } else {
    root = window;
    root.EventEmitter = EventEmitter;
  }

  // By default EventEmitters will print a warning if more than
  // 10 listeners are added to it. This is a useful default which
  // helps finding memory leaks.
  //
  // Obviously not all Emitters should be limited to 10. This function allows
  // that to be increased. Set to zero for unlimited.
  var defaultMaxListeners = 10;
  EventEmitter.prototype.setMaxListeners = function(n) {
    if (!this._events) this._events = {};
    this._maxListeners = n;
  };


  EventEmitter.prototype.emit = function() {
    var type = arguments[0];
    // If there is no 'error' event listener then throw.
    if (type === 'error') {
      if (!this._events || !this._events.error ||
        (isArray(this._events.error) && !this._events.error.length)) {
        if (this.domain) {
          var er = arguments[1];
          er.domain_emitter = this;
          er.domain = this.domain;
          er.domain_thrown = false;
          this.domain.emit('error', er);
          return false;
        }

        if (arguments[1] instanceof Error) {
          throw arguments[1]; // Unhandled 'error' event
        } else {
          throw new Error("Uncaught, unspecified 'error' event.");
        }
        return false;
      }
    }

    if (!this._events) return false;
    var handler = this._events[type];
    if (!handler) return false;

    if (typeof handler == 'function') {
      if (this.domain) {
        this.domain.enter();
      }
      switch (arguments.length) {
        // fast cases
        case 1:
          handler.call(this);
          break;
        case 2:
          handler.call(this, arguments[1]);
          break;
        case 3:
          handler.call(this, arguments[1], arguments[2]);
          break;
          // slower
        default:
          var l = arguments.length;
          var args = new Array(l - 1);
          for (var i = 1; i < l; i++) args[i - 1] = arguments[i];
          handler.apply(this, args);
      }
      if (this.domain) {
        this.domain.exit();
      }
      return true;

    } else if (isArray(handler)) {
      if (this.domain) {
        this.domain.enter();
      }
      var l = arguments.length;
      var args = new Array(l - 1);
      for (var i = 1; i < l; i++) args[i - 1] = arguments[i];

      var listeners = handler.slice();
      for (var i = 0, l = listeners.length; i < l; i++) {
        listeners[i].apply(this, args);
      }
      if (this.domain) {
        this.domain.exit();
      }
      return true;

    } else {
      return false;
    }
  };

  EventEmitter.prototype.addListener = function(type, listener) {
    if ('function' !== typeof listener) {
      throw new Error('addListener only takes instances of Function');
    }

    if (!this._events) this._events = {};

    // To avoid recursion in the case that type == "newListeners"! Before
    // adding it to the listeners, first emit "newListeners".
    this.emit('newListener', type, typeof listener.listener === 'function' ?
      listener.listener : listener);

    if (!this._events[type]) {
      // Optimize the case of one listener. Don't need the extra array object.
      this._events[type] = listener;
    } else if (isArray(this._events[type])) {

      // If we've already got an array, just append.
      this._events[type].push(listener);

    } else {
      // Adding the second element, need to change to array.
      this._events[type] = [this._events[type], listener];

    }

    // Check for listener leak
    if (isArray(this._events[type]) && !this._events[type].warned) {
      var m;
      if (this._maxListeners !== undefined) {
        m = this._maxListeners;
      } else {
        m = defaultMaxListeners;
      }

      if (m && m > 0 && this._events[type].length > m) {
        this._events[type].warned = true;
        console.error('(node) warning: possible EventEmitter memory ' +
          'leak detected. %d listeners added. ' +
          'Use emitter.setMaxListeners() to increase limit.',
          this._events[type].length);
        console.trace();
      }
    }

    return this;
  };

  EventEmitter.prototype.on = EventEmitter.prototype.addListener;

  EventEmitter.prototype.once = function(type, listener) {
    if ('function' !== typeof listener) {
      throw new Error('.once only takes instances of Function');
    }

    var self = this;

    function g() {
      self.removeListener(type, g);
      listener.apply(this, arguments);
    };

    g.listener = listener;
    self.on(type, g);

    return this;
  };

  EventEmitter.prototype.removeListener = function(type, listener) {
    if ('function' !== typeof listener) {
      throw new Error('removeListener only takes instances of Function');
    }

    // does not use listeners(), so no side effect of creating _events[type]
    if (!this._events || !this._events[type]) return this;

    var list = this._events[type];

    if (isArray(list)) {
      var position = -1;
      for (var i = 0, length = list.length; i < length; i++) {
        if (list[i] === listener ||
          (list[i].listener && list[i].listener === listener)) {
          position = i;
          break;
        }
      }

      if (position < 0) return this;
      list.splice(position, 1);
    } else if (list === listener ||
      (list.listener && list.listener === listener)) {
      delete this._events[type];
    }

    return this;
  };

  EventEmitter.prototype.removeAllListeners = function(type) {
    if (arguments.length === 0) {
      this._events = {};
      return this;
    }

    var events = this._events && this._events[type];
    if (!events) return this;

    if (isArray(events)) {
      events.splice(0);
    } else {
      this._events[type] = null;
    }

    return this;
  };

  EventEmitter.prototype.listeners = function(type) {
    if (!this._events) this._events = {};
    if (!this._events[type]) this._events[type] = [];
    if (!isArray(this._events[type])) {
      this._events[type] = [this._events[type]];
    }
    return this._events[type];
  }
})();

function createClient() {
  var EventEmitter = window.EventEmitter;
  var dataCode = new DataCode()
  if (typeof Object.create !== 'function') {
    Object.create = function(o) {
      function F() {}
      F.prototype = o;
      return new F();
    };
  }
  var client = Object.create(EventEmitter.prototype);
  var reqId = 0;
  var callbacks = {};
  var websocket = null

  client.init = function(ipCon, cb) {
    if(websocket){
      if (cb)
        cb()
      return
    }
    websocket = new WebSocket(ipCon)
    websocket.binaryType = "arraybuffer"
    websocket.onopen = function(evt) {
      console.log('连接服务器')
      if (cb)
        cb()
    };
    websocket.onclose = function(evt) {
      client.disconnect()
      if ($('#loginMain').html()) {} else {
        loadLoginMain()
        errorPopOpen('服务器连接已断开')
      }

      if(heartbeatTimer){
        clearInterval(heartbeatTimer)
      }
    }
    websocket.onmessage = function(message) {
      if (message.data instanceof ArrayBuffer) {
        var arrayBuff = new Uint8Array(message.data)
        var buff = []
        var length = arrayBuff.length
        for (var i = 0; i < length; i++) {
          buff[i] = arrayBuff[i]
        }
        data = dataCode.decodeBuffer(buff)
        data = JSON.parse(data)
        console.log(data)
        if (message.data.length < 80) {
          console.log('接  收: ' + message.data)
        } else {
          console.log('接  收: ')
          console.log(data.msg)
        }
        if (data.reqId) {
          var i = data.reqId
          if (callbacks[i]) {
            callbacks[i](data.msg)
            delete callbacks[i]
          }
        }
        if (data.route) {
          console.log('==监听消息: ' + data.route)
          console.log(data.msg)
          client.emit(data.route, data.msg)
        }
      }
    }
    websocket.onerror = function(evt) {
      client.disconnect()
      console.log('onerror')
      if ($('#loginMain').html()) {
        $('.warning1').html('*服务器连接失败,请检查网络是否连接')
        setTimeout(function() {
          $('.warning1').html('')
        }, 2500)
      } else {
        loadLoginMain()
        errorPopOpen('服务器连接已断开')
      }

      if(heartbeatTimer){
        clearInterval(heartbeatTimer)
      }
    }
  };

  client.disconnect = function() {
    if (websocket) {
      if (websocket.disconnect) websocket.disconnect()
      if (websocket.close) websocket.close()
      websocket = null
    }
  }

  client.request = function(route, msg, cb) {
    reqId += 1
    var data = {
      route: route,
      msg: msg,
      reqId: reqId
    }

    callbacks[reqId] = cb
    data = JSON.stringify(data)
    console.log('发送: ' + data)
    data = dataCode.encodeStr(data)
    websocket.send(data)
  }

  return client
}


function strToUtf8Array(str) {
  if (typeof(str) !== "string") {
    throw new TypeError("toUTF8 function only accept a string as its parameter.");
  }
  var ret = [];
  var c1, c2, c3;
  var cc = 0;
  for (var i = 0, l = str.length; i < l; i++) {
    cc = str.charCodeAt(i);
    if (cc > 0xFFFF) {
      throw new Error("InvalidCharacterError");
    }
    if (cc > 0x80) {
      if (cc < 0x07FF) {
        // c1 = String.fromCharCode( ( cc >>>  6 ) | 0xC0 );
        //  c2 = String.fromCharCode( ( cc & 0x3F ) | 0x80 );
        c1 = (cc >>> 6) | 0xC0
        c2 = (cc & 0x3F) | 0x80
        ret.push(c1, c2);
      } else {
        //  c1 = String.fromCharCode(   ( cc >>> 12 )          | 0xE0 );
        // c2 = String.fromCharCode( ( ( cc >>>  6 ) & 0x3F ) | 0x80 );
        // c3 = String.fromCharCode(   ( cc          & 0x3F ) | 0x80 );
        c1 = (cc >>> 12) | 0xE0
        c2 = ((cc >>> 6) & 0x3F) | 0x80
        c3 = (cc & 0x3F) | 0x80
        ret.push(c1, c2, c3);
      }
    } else {
      ret.push(cc);
    }
  }
  return ret
}

function utf8ArrayToStr(array) {
  var out, i, len, c;
  var char2, char3;

  out = "";
  len = array.length;
  i = 0;
  while (i < len) {
    c = array[i++];
    switch (c >> 4) {
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
        // 0xxxxxxx
        out += String.fromCharCode(c);
        break;
      case 12:
      case 13:
        // 110x xxxx   10xx xxxx
        char2 = array[i++];
        out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
        break;
      case 14:
        // 1110 xxxx  10xx xxxx  10xx xxxx
        char2 = array[i++];
        char3 = array[i++];
        out += String.fromCharCode(((c & 0x0F) << 12) |
          ((char2 & 0x3F) << 6) |
          ((char3 & 0x3F) << 0));
        break;
    }
  }

  return out;
}


function DataCode() {
  var box = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64];
  var keystr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'm', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'N', 'M', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'S', 'Y', 'Z']

  function getKsa(keys) {
    var length = keys.length
    var tmp
    var s = box.slice()
    var j = 0
    for (var i = 0; i < 64; i++) {
      j = (j + s[i] + keys[i % length]) % 64
      tmp = s[i]
      s[i] = s[j]
      s[j] = tmp
    }
    return s
  }

  function body(buff, s) {
    var i = 0,
      j = 0,
      t = 0,
      tmp,
      len = buff.length
    for (var k = 0; k < len; k++) {
      i = (i + 1) % 64
      j = (j + s[i]) % 64
      tmp = s[i]
      s[i] = s[j]
      s[j] = tmp
      t = (s[i] + s[j]) % 64 /**/
      t = k % 64
      buff[k] ^= s[t]
    }
    return buff
  }

  function encodeStr(str) {
    var buff = strToUtf8Array(str)
    var key = ''
    for (var i = 0; i < 4; i++) {
      key += keystr[Math.floor(Math.random() * 62)]
    }
    var keys = strToUtf8Array(key)
    var s = getKsa(keys)
    var dataBuff = body(buff, s)
    var encode = []
    encode = keys.concat(dataBuff)
    encode = new Uint8Array(encode)
    return encode
  }
  this.encodeStr = encodeStr

  function decodeBuffer(buff) {
    var keys = buff.slice(0, 4)
    var dataBuff = buff.slice(4)
    var s = getKsa(keys)
    var data = body(dataBuff, s)
    return utf8ArrayToStr(data)
  }
  this.decodeBuffer = decodeBuffer
}
